// RingBuffer.cop - ring (round robin) buffer implementation

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "RingBuffer.h"
#include "errno.h"
#include "DebugLogger.h"

#define SIGNAL_STOP  1
#define SIGNAL_START_READING 2
#define SIGNAL_STOP_

// ringbuffer read&write return code, must be <0. >=0 are reserved for number of bytes read/written
#define RINGBUFFER_RW_SYS_EXIT -1
#define RINGBUFFER_RW_READING_NOT_STARTED -2
#define RINGBUFFER_RW_READING_WAIT_TIMEOUT -3

#define READ_EXIT ShowBuffer( 2);
#define WRITE_EXIT ShowBuffer( 1);

#define PTHREAD_COND_TIMEWAIT(RET, COND, MUTUX, SEC)\
				timespec ts; \
				clock_gettime(CLOCK_REALTIME, &ts); \
				ts.tv_sec += SEC; \
				RET = pthread_cond_timedwait( &COND, &MUTUX, &ts);


// Data Ring buffer constructor
RingBuffer::RingBuffer(int sizeBytes)
{
	if ( sizeBytes) {
		m_pData = new unsigned char[sizeBytes];
		memset(m_pData, 0, sizeBytes );
	}
	else {
		m_pData = NULL;
	}
	m_size = sizeBytes;
	m_readPos = 0; // read position for the next read.
	m_writePos = 0; // write position for the next write.
	m_bBufferLooped = false;
	m_bReadingStarted = false;
	m_bExit = false;

    m_nReadSize = 1;
    strcpy( m_szBufferFileName, "ringbuffer.raw");

	// Initialize ring buffer mutexpthread_cond_init (&count_threshold_cv,
    pthread_mutexattr_t mutexAtrRingBuffer;

    pthread_mutexattr_init(&mutexAtrRingBuffer);
    pthread_mutexattr_settype(&mutexAtrRingBuffer, PTHREAD_MUTEX_RECURSIVE);

    pthread_mutex_init(&m_ring_buffer_mutex, &mutexAtrRingBuffer);
    //pthread_mutex_init(&m_ring_buffer_mutex, NULL);

	pthread_cond_init(&m_condSpaceAvailable, NULL);
	pthread_cond_init(&m_condReading, NULL);
	pthread_cond_init(&m_condReadingThreadCreated, NULL);

	m_threadReading = 0;
}

void RingBuffer::InitSize(int sizeBytes)
{
	if ( sizeBytes) {
		m_pData = new unsigned char[sizeBytes];
		memset(m_pData, 0, sizeBytes );
	}
	else {
		m_pData = NULL;
	}
	m_size = sizeBytes;
}

// Data Ring buffer destructor
RingBuffer::~RingBuffer()
{
	if ( m_threadReading)
		Exit();

	// Delete ring buffer memory allocation
	if ( m_pData != NULL) {
		delete[] m_pData;
		m_pData = NULL;
	}

	// Delete ring buffer mutex
	pthread_mutex_destroy(&m_ring_buffer_mutex);
	pthread_cond_destroy(&m_condSpaceAvailable);
	pthread_cond_destroy(&m_condReading);
	pthread_cond_destroy(&m_condReadingThreadCreated);
}

// Set all data to 0 and flag buffer as empty.
bool RingBuffer::Empty(void)
{
	memset(m_pData, 0, m_size);
	m_readPos = 0;
	m_writePos = 0;
	m_bBufferLooped = false;
	return true;
}

int RingBuffer::WriteBuffer(unsigned char *ptrTo, int& nWritePos, unsigned char *&ptrFrom, int& nWriteLen)
{
	memcpy(ptrTo + nWritePos, ptrFrom, nWriteLen);
	nWritePos += nWriteLen;
	ptrFrom += nWriteLen;
	return nWriteLen;
}

int RingBuffer::ResumeReading()
{
	pthread_mutex_lock(&m_ring_buffer_mutex);
	m_bReadingStarted = true;
	m_readPos = m_writePos;
	pthread_cond_signal(&m_condReading);
 	pthread_mutex_unlock(&m_ring_buffer_mutex);
 	return 0;
}

int RingBuffer::PauseReading()
{
	pthread_mutex_lock(&m_ring_buffer_mutex);
	m_bReadingStarted = false;
	pthread_cond_signal(&m_condReading);
 	pthread_mutex_unlock(&m_ring_buffer_mutex);
 	return 0;
}

int RingBuffer::Exit()
{
	DEBUGLOG0("Calling Exit\n");
	pthread_mutex_lock(&m_ring_buffer_mutex);
	m_bExit = true;
	pthread_cond_signal(&m_condReading);
 	pthread_mutex_unlock(&m_ring_buffer_mutex);

	if ( m_threadReading != NULL) {
		void *status;
		DEBUGLOG0( "Waiting for Join ThreadWorker_Reading\n");
		int rc = pthread_join(m_threadReading, &status);
		if (rc) {
			printf("ERROR; return code from pthread_join() is %d\n", rc);
		}
		m_threadReading = 0;
		DEBUGLOG0( "Finished Joining ThreadWorker_Reading\n");
	}

 	return 0;
}

// Write to the ring buffer and reposition buffer pointers
int RingBuffer::Write(unsigned char *dataPtr, int numBytes)
{
	// If there's nothing to write or no room available, we can't write anything.
	if (dataPtr == NULL || numBytes <= 0) { // || m_writeBytesAvail == 0) {
		return 0;
	}

	// Lock access to ring buffer
	pthread_mutex_lock(&m_ring_buffer_mutex);

	if ( m_bExit) {
		WRITE_EXIT;
	 	pthread_mutex_unlock(&m_ring_buffer_mutex);
		return RINGBUFFER_RW_SYS_EXIT;
	}

	int nRemainingBytes = numBytes;

	// we continuously write in a loop until we finish writing all the data
	while ( nRemainingBytes>0) {
		if ( m_bReadingStarted == true) {
			// there is another thread reading the buffer, we need to make sure we don't
			// overwrite the buffer (writing faster than the reading).

			if ( m_bBufferLooped && m_writePos <= m_readPos) {
				// write some data before the readPtr. then wait for empty space
				int nAvailableSpace = m_readPos - m_writePos;
				if ( nAvailableSpace >= nRemainingBytes) {
					nRemainingBytes -= WriteBuffer(m_pData, m_writePos, dataPtr, nRemainingBytes);
					break;
				}
				else {
					nRemainingBytes -= WriteBuffer(m_pData, m_writePos, dataPtr, nAvailableSpace);
					int iRetWait = 0;
					PTHREAD_COND_TIMEWAIT( iRetWait, m_condSpaceAvailable, m_ring_buffer_mutex, 1);
					if ( iRetWait == 0)
						continue;
					else {
						DEBUGLOG0( "Buffer full. Wait for reading thread timed out.");
						break;
					}
				}
			}
		}

		if (nRemainingBytes >= m_size - m_writePos) {
			// write data to the end of buffer. loop around
			int nAvailableSpace = m_size - m_writePos;
			nRemainingBytes -= WriteBuffer(m_pData, m_writePos, dataPtr, nAvailableSpace);
			m_bBufferLooped = true;
			m_writePos = 0;
		} else {
			// write the buffer
			nRemainingBytes -= WriteBuffer(m_pData, m_writePos, dataPtr, nRemainingBytes);
		}
	}

	if ( m_bReadingStarted == true) {
		pthread_cond_signal(&m_condReading);
		//DEBUGLOG0("m_condReading signaled\n");
	}

	// Release access to ring buffer
 	WRITE_EXIT;
 	pthread_mutex_unlock(&m_ring_buffer_mutex);

	return numBytes;
}

// Read from the ring buffer and reposition buffer pointers
int RingBuffer::ReadPrevBuffer(unsigned char *dataPtr, int numBytes)
{
	if (dataPtr == NULL || numBytes <= 0) { // || m_writeBytesAvail < numBytes) {
		return 0;
	}

	pthread_mutex_lock(&m_ring_buffer_mutex);

	bool bLoopRead = false;
	if ( numBytes <= m_writePos) {
		// we have enough bytes from left end, we don't need to loop around
	}
	else if ( m_bBufferLooped && numBytes <= m_size) {
		// we don't have enough bytes from left end, and the written data is
		// looped. we will need to read data from left end and right end.
		bLoopRead = true;
	}
	else if ( m_bBufferLooped && numBytes > m_size) {
		// the requested size is over the size limit. reduce the requested size
		bLoopRead = true;
		numBytes = m_size;
	}
	else {
		numBytes = m_writePos;
	}

	if ( bLoopRead) {
		int nFirstHalfLen = numBytes-m_writePos;
		if ( nFirstHalfLen != 0) {
			memcpy(dataPtr, m_pData + m_size - nFirstHalfLen, nFirstHalfLen);
			memcpy(dataPtr+nFirstHalfLen, m_pData, m_writePos);
		} else {
			memcpy(dataPtr, m_pData, numBytes);
		}
	}
	else {
		memcpy(dataPtr, m_pData+m_writePos-numBytes, numBytes);
	}

 	pthread_mutex_unlock(&m_ring_buffer_mutex);
 	return numBytes;
}

// Read from the ring buffer and reposition buffer pointers
int RingBuffer::Read(unsigned char *dataPtr, int numBytes)
{
	// If there's nothing to read or no data available, then we can't read anything.
	if (dataPtr == NULL || numBytes <= 0) { // || m_writeBytesAvail < numBytes) {
		return 0;
	}

	// Lock access to ring buffer
	pthread_mutex_lock(&m_ring_buffer_mutex);

	int iRetWait=0;

	int nAvailableData = 0;

	if ( m_bBufferLooped) {
		nAvailableData = m_size - m_readPos + m_writePos;
	}
	else {
		nAvailableData = m_writePos - m_readPos;
	}

	if ( m_bExit) {
		READ_EXIT;
	 	pthread_mutex_unlock(&m_ring_buffer_mutex);
		return RINGBUFFER_RW_SYS_EXIT;
	}

	if ( m_bReadingStarted == false) {
		READ_EXIT;
	 	pthread_mutex_unlock(&m_ring_buffer_mutex);
		return RINGBUFFER_RW_READING_NOT_STARTED;
	}

	if ( nAvailableData <= 0) {
	 	PTHREAD_COND_TIMEWAIT( iRetWait, m_condReading, m_ring_buffer_mutex, 5);
		if ( iRetWait == ETIMEDOUT) {
			DEBUGLOG1( "conditionReading timed out %08x\n", &m_condReading);
			//return RINGBUFFER_RW_READING_WAIT_TIMEOUT;
		}
		else if ( iRetWait == 0){
			DEBUGLOG0( "conditionReading signaled\n");
		}
		else {
			DEBUGLOG1( "conditionReading wait returns %i\n", iRetWait);
		}

		if ( m_bExit) {
			READ_EXIT;
		 	pthread_mutex_unlock(&m_ring_buffer_mutex);
			return RINGBUFFER_RW_SYS_EXIT;
		}

		if ( m_bReadingStarted == false) {
			READ_EXIT;
		 	pthread_mutex_unlock(&m_ring_buffer_mutex);
			return RINGBUFFER_RW_READING_NOT_STARTED;
		}
	}

	// Simultaneously keep track of how many bytes we've read
	// and our position in the outgoing buffer
 	if (numBytes > nAvailableData){
 		numBytes = nAvailableData;
 	}

 	if ( numBytes <= m_size - m_readPos) {
		memcpy(dataPtr, m_pData + m_readPos, numBytes);
		m_readPos += numBytes;
 	}
 	else {
		int len = m_size - m_readPos;
		memcpy(dataPtr, m_pData + m_readPos, len);
		memcpy(dataPtr + len, m_pData, numBytes - len);
		m_readPos = len;
 		m_bBufferLooped = false;
 	}

 	if ( m_readPos >= m_size) {
 		m_readPos %= m_size;
 		m_bBufferLooped = false;
 	}

 	if ( numBytes > 0)
 		pthread_cond_signal(&m_condSpaceAvailable);

 	// Release access to ring buffer
	READ_EXIT;
 	pthread_mutex_unlock(&m_ring_buffer_mutex);

	return (numBytes);
}


// Lock and show the current ring buffer contents
void RingBuffer::ShowBuffer(int nAction)
{
	// Lock access to ring buffer
	pthread_mutex_lock(&m_ring_buffer_mutex);

	if ( nAction == 1) {
		DEBUGLOG4( "RingBuffer WRITEPOS:{%d} readpos:<%d> loop:%c reading:%s\n",
				this->m_writePos, this->m_readPos,
				m_bBufferLooped ? 'Y':'N',
				m_bReadingStarted ? "Y":"N");
	}
	else if ( nAction == 2) {
		DEBUGLOG4( "RingBuffer writepos:{%d} READPOS:<%d> loop:%c reading:%s\n",
				this->m_writePos, this->m_readPos,
				m_bBufferLooped ? 'Y':'N',
				m_bReadingStarted ? "Y":"N");
	}
	else {
		DEBUGLOG4( "RingBuffer writepos:{%d} readpos:<%d> loop:%c reading:%s\n",
				this->m_writePos, this->m_readPos,
				m_bBufferLooped ? 'Y':'N',
				m_bReadingStarted ? "Y":"N");
	}

	for (int i = 0; i < m_size; i++) {
		if ( m_writePos == i && m_readPos == i) {
			DEBUGLOG1_( "{%02x>", (unsigned int)(*(m_pData + i)));
		}
		else if ( m_writePos == i) {
			DEBUGLOG1_( "{%02x}", (unsigned int)(*(m_pData + i)));
		}
		else if ( m_readPos == i) {
			DEBUGLOG1_( "<%02x>", (unsigned int)(*(m_pData + i)));
		}
		else {
			DEBUGLOG1_( " %02x ", (unsigned int)(*(m_pData + i)));
		}

		if (i!=0 && !(i % 32)) {
			DEBUGLOG0_( "\n");
		}
	}
	DEBUGLOG0_( "\n");

	// 1024Release access to ring buffer
 	pthread_mutex_unlock(&m_ring_buffer_mutex);
}

// Get size of ring buffer
int RingBuffer::GetSize(void)
{
	return m_size;
}

void RingBuffer::SignalReadingThreadCreated()
{
    pthread_mutex_lock( &m_ring_buffer_mutex);
	pthread_cond_signal(&m_condReadingThreadCreated);
    pthread_mutex_unlock( &m_ring_buffer_mutex);
}

void* RingBuffer::ThreadWorker_Reading(void *data)
{
	DEBUGLOG0("ThreadWorker_Reading started\n");
	ThreadData_Reading *pThreadDataReading = (ThreadData_Reading*)data;
	RingBuffer *pRingBuffer = pThreadDataReading->pRingBuffer;
	pRingBuffer->SignalReadingThreadCreated();

    FILE* fileCapture = NULL;
    if ( pThreadDataReading->bSaveToFile) {
        fileCapture = fopen( pRingBuffer->GetBufferFileName(), "w+b");
    }

	unsigned char buf[1024];
	while ( true) {
		int nRead = pRingBuffer->Read( buf, pRingBuffer->GetReadSize());
		if ( nRead == RINGBUFFER_RW_SYS_EXIT) {
			break;
		}

		if ( nRead>0 && fileCapture) {
            fwrite( buf, sizeof(char), nRead, fileCapture);
        }

		usleep( 20000);
	}

    if ( fileCapture) {
    	DEBUGLOG1( "Closing %s\n", pRingBuffer->GetBufferFileName());
        fflush( fileCapture);
        fclose( fileCapture);
    }

    DEBUGLOG0( "ThreadWorker_Reading exits\n");

    pthread_exit(NULL); // Get number of bytes to capture

}

int RingBuffer::StartReadingThread()
{
	m_threaddataReading.pRingBuffer = this;
	m_threaddataReading.bSaveToFile = true;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    int err = pthread_create(&m_threadReading, &attr, ThreadWorker_Reading, (void *)&m_threaddataReading);
    if (err) {
        printf("ERROR; return code from pthread_create() is %d\n", err);
        return -1;
    }
    pthread_attr_destroy(&attr);

    pthread_mutex_lock( &m_ring_buffer_mutex);
	int iRetWait = 0;
	PTHREAD_COND_TIMEWAIT( iRetWait, m_condReadingThreadCreated, m_ring_buffer_mutex, 5);
    pthread_mutex_unlock( &m_ring_buffer_mutex);
}
